package redis

import (
	"gitlab.local/DO-module/new-filemanage-module/src/service/phpsessgo"
	"gitlab.local/DO-module/new-filemanage-module/src/service/phpsessgo/phpencode"
	"net/http"
	"strings"
	"sync"
	"time"
)

const (
	DefaultSessionName    = "TMSESSNAME"
	DefaultRedisKeyPrefix = "PHPREDIS_SESSION:"
)

type SessionManager interface {
	Start(w http.ResponseWriter, r *http.Request) (session *phpsessgo.Session, err error)
	ShareID(r *http.Request, session *phpsessgo.Session)
	Save(session *phpsessgo.Session) error
	Remove(id string) error
	GetTTL(id string) time.Duration
	Persist(id string) error
	SessionName() string
	SIDCreator() phpsessgo.SessionIDCreator
	Handler() phpsessgo.SessionHandler
	Encoder() phpsessgo.SessionEncoder
	SetCookieString(string) string
}

func NewSessionManager(
	sessionName string,
	sidCreator SessionIDCreator,
	handler SessionHandler,
	encoder SessionEncoder,
	config phpsessgo.SessionManagerConfig,
) SessionManager {

	return &sessionManager{
		sessionName: sessionName,
		sidCreator:  sidCreator,
		handler:     handler,
		encoder:     encoder,
		config:      config,
	}
}

// SessionManager handle session creation/modification
type sessionManager struct {
	sessionName  string
	sidCreator   SessionIDCreator
	handler      phpsessgo.SessionHandler
	encoder      phpsessgo.SessionEncoder
	config       phpsessgo.SessionManagerConfig
	shareSession sync.Map
}

// Start is adoption of PHP start_session() to return current active session
func (m *sessionManager) Start(w http.ResponseWriter, r *http.Request) (session *phpsessgo.Session, err error) {
	session = NewSession()

	var raw, sessionID string
	var phpSession phpencode.PhpSession
	key := r.Header.Get("X-Real-IP")
	if sid, ok := m.shareSession.Load(key); ok {
		sessionID = sid.(string)
		if r.Header.Get("X-Shared-Delete") == "true" {
			m.shareSession.Delete(key)
		}
	}
	var cookieSid string
	if sessionID == "" {
		if sid := r.Header.Get("TMSESSNAME"); sid != "" {
			cookieSid = sid
		} else {
			cookieSid = m.getFromCookies(r.Cookies())
		}
		sessionID = cookieSid
	}
	if sessionID == "" {
		sessionID = m.sidCreator.CreateSID()
		session.SessionID = sessionID
		http.SetCookie(w, &http.Cookie{
			Name:     m.sessionName,
			Value:    sessionID,
			HttpOnly: m.config.CookieHttpOnly,
			Path:     m.config.CookiePath,
			Domain:   m.config.CookieDomain,
			Expires:  time.Now().Add(720 * time.Hour),
		})
		return
	}

	session.SessionID = sessionID
	if sessionID != cookieSid {
		http.SetCookie(w, &http.Cookie{
			Name:     m.sessionName,
			Value:    sessionID,
			HttpOnly: m.config.CookieHttpOnly,
			Path:     m.config.CookiePath,
			Domain:   m.config.CookieDomain,
			Expires:  time.Now().Add(720 * time.Hour),
		})
	}
	raw, err = m.handler.Read(sessionID)
	if err != nil {
		return
	}

	phpSession, err = m.encoder.Decode(raw)
	if err != nil {
		return
	}
	session.Value = phpSession

	return
}

func (m *sessionManager) ShareID(r *http.Request, session *phpsessgo.Session) {
	key := r.Header.Get("X-Real-IP")
	m.shareSession.Store(key, session.SessionID)
}

// Save the session
func (m *sessionManager) Save(session *phpsessgo.Session) error {
	sessionData, err := m.encoder.Encode(session.Value)
	if err != nil {
		return err
	}

	return m.handler.Write(session.SessionID, sessionData)
}

// Remove the session
func (m *sessionManager) Remove(id string) error {
	return m.handler.Remove(id)
}

func (m *sessionManager) Persist(id string) error {
	return m.handler.Persist(id)
}

func (m *sessionManager) GetTTL(id string) time.Duration {
	return m.handler.TTL(id)
}

func (m *sessionManager) SessionName() string {
	return m.sessionName
}

func (m *sessionManager) SIDCreator() phpsessgo.SessionIDCreator {
	return m.sidCreator
}

func (m *sessionManager) Handler() phpsessgo.SessionHandler {
	return m.handler
}

func (m *sessionManager) Encoder() phpsessgo.SessionEncoder {
	return m.encoder
}

func (m *sessionManager) getFromCookies(cookies []*http.Cookie) string {
	for _, cookie := range cookies {
		if cookie.Name == m.sessionName {
			return cookie.Value
		}
	}
	return ""
}

// SetCookieString naive approach to get lowercase Domain and Path attribute
func (m *sessionManager) SetCookieString(sessionID string) string {
	var builder strings.Builder

	builder.WriteString(m.SessionName())
	builder.WriteString("=")
	builder.WriteString(sessionID)
	builder.WriteString("; ")

	if m.config.CookiePath != "" {
		builder.WriteString("path=")
		builder.WriteString(m.config.CookiePath)
		builder.WriteString("; ")
	}

	if m.config.CookieDomain != "" {
		builder.WriteString("domain=")
		builder.WriteString(m.config.CookieDomain)
		builder.WriteString("; ")
	}

	if m.config.CookieHttpOnly {
		builder.WriteString("httponly")
	}

	return builder.String()
}
